﻿using ELFSharp.ELF;
using ELFSharp.ELF.Sections;
using MySoftLab.DapFlashCore.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace MySoftLab.DapFlashCore.Targets.Packs
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct FlashSectors
    {
        public uint szSector;    // Sector Size in Bytes
        public uint AddrSector;    // Address of Sector
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct FlashDevice
    {
        public ushort Vers;    // Version Number and Architecture

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DevName;    // Device Name and Description
        public ushort DevType;    // Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
        public uint DevAdr;    // Default Device Start Address
        public uint szDev;    // Total Size of Device
        public uint szPage;    // Programming Page Size
        public uint Res;    // Reserved for future Extension
        public byte valEmpty;    // Content of Erased Memory

        public uint toProg;    // Time Out of Program Page Function
        public uint toErase;    // Time Out of Erase Sector Function

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
        public FlashSectors[] sectors;
    }

    public class FlashAlgoExtracter
    {
        public static uint[] FLASH_BLOB_HEADER = [0xE7FDBE00];
        public static int FLASH_BLOB_HEADER_SIZE = FLASH_BLOB_HEADER.Length * 4;
        public const int MIN_STACK_SIZE = 512;
        public const int PAGE_BUFFER_ALIGN = 16;

        public FlashAlgoExtracter()
        {

        }

        private SymbolEntry<uint>? GetSymbolEntry(IELF elf, string name)
        {
            return ((ISymbolTable)elf.GetSection(".symtab")).Entries.Where(x => x.Name == name).FirstOrDefault() as SymbolEntry<uint>;
        }

        public FlashAlgo Load(string fileName)
        {
            using var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
            return Load(fileStream);
        }

        /*
         * 内存布局
         * [<--stack] [buf2] [buf1] [code]
        ^* ram start                   ^ ram end
         */
        public FlashAlgo Load(Stream stream)
        {
            var elf = ELFReader.Load(stream, true);

            var flashDev = GetSymbolEntry(elf, "FlashDevice");

            var bytes = flashDev.PointedSection.GetContents();

            var initEntry = GetSymbolEntry(elf, "Init");
            var uninitEntry = GetSymbolEntry(elf, "UnInit");
            var eraseChipEntry = GetSymbolEntry(elf, "EraseChip");
            var eraseSectorEntry = GetSymbolEntry(elf, "EraseSector");
            var programPageEntry = GetSymbolEntry(elf, "ProgramPage");

            // 读取FlashDevice结构体
            nint ptr = Marshal.AllocHGlobal(bytes.Length);

            FlashDevice? flashInfo = null;

            try
            {
                // 将byte数组复制到非托管内存
                Marshal.Copy(bytes, 0, ptr, bytes.Length);

                // 将非托管内存的内容转换为结构体
                flashInfo = (FlashDevice)Marshal.PtrToStructure(ptr, typeof(FlashDevice));
            }
            finally
            {
                // 释放非托管内存
                Marshal.FreeHGlobal(ptr);
            }

            var prgCode = elf.GetSection("PrgCode") as ProgBitsSection<uint>;
            var prgData = elf.GetSection("PrgData") as ProgBitsSection<uint>;

            List<uint> instructions = [.. FLASH_BLOB_HEADER];

            // 创建instructions
            if (prgCode != null)
            {
                var prgCodeBytes = prgCode.GetContents();
                for (int i = 0; i < prgCodeBytes.Length / 4; i++)
                {
                    instructions.Add(BitConverter.ToUInt32(prgCodeBytes, i * 4));
                }
            }

            if (prgData != null)
            {
                var prgDataBytes = prgData.GetContents();
                for (int i = 0; i < prgDataBytes.Length / 4; i++)
                {
                    instructions.Add(BitConverter.ToUInt32(prgDataBytes, i * 4));
                }
            }

            //for (int i = 0; i < instructions.Count; i++)
            //{
            //    if (i % 8 == 0)
            //    {
            //        Console.WriteLine();
            //    }
            //    Console.Write($"0x{instructions[i]:x8} ");
            //}
            //Console.WriteLine();

            // Start at end of RAM and work backwards.
            // TODO 写死RAM大小，实际需要从pdsc读取
            // uint ramLength = 0x800;
            uint ramStart = 0x20000000;
            uint ramLength = 0x8000;

            uint addr = ramStart + ramLength;

            // Console.WriteLine($"instruction len={instructions.Count}");

            // Load Address
            addr -= (uint)(instructions.Count * 4);
            uint addrLoad = addr;

            // DataBuffer
            var blockSize = flashInfo.Value.szPage;
            uint unalignedBufferAddr = addr - blockSize;
            addr = CommonUtils.AlignDown(unalignedBufferAddr, PAGE_BUFFER_ALIGN);
            var addrData = addr;

            if (addrData < ramStart)
            {
                throw new Exception("not enough memory space to fit flash algorithm");
            }

            var stackSizeOneBuf = addrData - ramStart;
            var stackSize = stackSizeOneBuf;
            var addrStack = addrData;
            var pageBuffers = new uint[] { addrData };

            uint codeStart = (uint)(addrLoad + FLASH_BLOB_HEADER_SIZE);

            uint eraseAllAddr = eraseChipEntry == null ? 0xffffffff : eraseChipEntry.Value;

            return new FlashAlgo()
            {
                LoadAddress = addrLoad,
                Instructions = instructions.ToArray(),
                PcInit = codeStart + initEntry.Value,
                PcUnInit = codeStart + uninitEntry.Value,
                PcEraseAll = codeStart + eraseAllAddr,
                PcEraseSector = codeStart + eraseSectorEntry.Value,
                PcProgramPage = codeStart + programPageEntry.Value,
                PageBuffers = pageBuffers.ToArray(),
                BeginData = pageBuffers[0],
                BeginStack = addrStack,
                EndStack = 0xffffffff,
                StaticBase = codeStart + prgData.LoadAddress,
                PageSize = blockSize,
                SectorSize = flashInfo.Value.sectors[0].szSector,
                MinProgramLength = blockSize,
                AnalyzerSupported = false,
            };
        }
    }
}
